package ru.hflabs.rcd.web.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Function;
import com.google.common.collect.Collections2;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.MessageSource;
import org.springframework.core.convert.ConversionException;
import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageConversionException;
import org.springframework.validation.BindException;
import org.springframework.validation.Errors;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.ServletRequestBindingException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.servlet.ModelAndView;
import ru.hflabs.rcd.exception.ApplicationException;
import ru.hflabs.rcd.exception.ApplicationValidationException;
import ru.hflabs.rcd.exception.constraint.IllegalPrimaryKeyException;
import ru.hflabs.rcd.model.Identifying;
import ru.hflabs.rcd.model.definition.ModelDefinition;
import ru.hflabs.rcd.service.IChangeService;
import ru.hflabs.rcd.service.IServiceFactory;
import ru.hflabs.rcd.web.model.ErrorBean;
import ru.hflabs.rcd.web.model.ErrorView;
import javax.annotation.Resource;
import java.util.Arrays;
import java.util.Collection;
import java.util.Locale;
import static ru.hflabs.rcd.service.ServiceUtils.extractSingleDocument;
/**
* Класс <class>ControllerTemplate</class> реализует базовый класс контроллеров страниц, содержищий обработку исключений
*
* @see ExceptionHandler
*/
public abstract class ControllerTemplate {
protected final Logger LOG = LoggerFactory.getLogger(getClass());
/** Постфикс идентификатора контроллера */
public static final String NAME_POSTFIX = ".controller";
/** Постфикс URI контроллера */
public static final String DATA_URI = "/data";
/** Сервис работы с сообщениями */
@Resource(name = "messageSource")
protected MessageSource messageSource;
/** Фабрика описания моделей */
@Resource(name = "modelDefinitionFactory")
protected IServiceFactory<ModelDefinition, Class<?>> modelDefinitionFactory;
/** Сервис конвертации объектов */
@Resource(name = "jacksonObjectMapper")
protected ObjectMapper objectMapper;
/** Размер страницы фильтрации объектов по умолчанию */
@Value("$web{paging.size}")
protected int defaultPagingSize;
/**
* Выполняет создание одного документа
*
* @param service сервис работы с документами
* @param target создаваемый документ
* @param needValidation флаг необходимости валидации документа перед созданием
* @return Возвращает обновленный документ
*/
protected static <T extends Identifying> T createSingleDocument(IChangeService<T> service, T target, boolean needValidation) {
return extractSingleDocument(service.create(Arrays.asList(target), needValidation));
}
/**
* Выполняет обновление одного документа
*
* @param service сервис работы с документами
* @param target обновляемый документ
* @param needValidation флаг необходимости валидации документа перед обновлением
* @return Возвращает обновленный документ
*/
protected static <T extends Identifying> T updateSingleDocument(IChangeService<T> service, T target, boolean needValidation) {
return extractSingleDocument(service.update(Arrays.asList(target), needValidation));
}
/**
* Выделяет название локализации для исключительной ситуации
*
* @param exception исключение
* @return Возвращает название локализации
*/
private static String extractExceptionName(Throwable exception) {
return exception.getClass().getSimpleName();
}
/**
* Создает и возвращает декоратор ошибки
*
* @param code код ошибки
* @param locale текущая локаль
* @param exception исключительная ситуация
* @param parameters параметры сообщения
* @return Возвращает созданный декоратор ошибки
*/
private ErrorBean createErrorBean(String code, Locale locale, Throwable exception, Object... parameters) {
return new ErrorBean(ImmutableList.of(messageSource.getMessage(code, parameters, exception.getMessage(), locale)));
}
/**
* Формируем декоратор ошибок валидации объекта
*
* @param errors ошибки валидации
* @param locale локализация
* @return Возвращает декоратор ошибок валидации
*/
protected ErrorBean doHandleValidationException(Errors errors, final Locale locale) {
Collection<String> globalErrors = Collections2.transform(errors.getGlobalErrors(), new Function<ObjectError, String>() {
@Override
public String apply(ObjectError input) {
return messageSource.getMessage(input, locale);
}
});
ImmutableMap.Builder<String, String> fieldErrors = ImmutableMap.builder();
for (FieldError error : errors.getFieldErrors()) {
fieldErrors.put(error.getField(), messageSource.getMessage(error, locale));
}
return new ErrorBean(globalErrors, fieldErrors.build());
}
@ExceptionHandler({
ServletRequestBindingException.class,
HttpMessageConversionException.class,
ConversionException.class
})
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public ErrorBean handleMissingParameterException(Exception exception, Locale locale) {
return createErrorBean(extractExceptionName(exception), locale, exception);
}
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public ErrorBean handleMethodArgumentNotValidException(MethodArgumentNotValidException exception, Locale locale) {
return doHandleValidationException(exception.getBindingResult(), locale);
}
@ExceptionHandler(BindException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
@ResponseBody
public ErrorBean handleBindException(BindException exception, Locale locale) {
return doHandleValidationException(exception.getBindingResult(), locale);
}
@ExceptionHandler(ApplicationValidationException.class)
@ResponseStatus(HttpStatus.CONFLICT)
@ResponseBody
public ErrorBean handleApplicationValidationException(ApplicationValidationException exception, Locale locale) {
return doHandleValidationException(exception.getErrors(), locale);
}
@ExceptionHandler(ApplicationException.class)
@ResponseStatus(HttpStatus.CONFLICT)
@ResponseBody
public ErrorBean handleApplicationException(ApplicationException exception, Locale locale) {
return createErrorBean(extractExceptionName(exception), locale, exception);
}
@ExceptionHandler(IllegalPrimaryKeyException.class)
@ResponseStatus(HttpStatus.GONE)
@ResponseBody
public ErrorBean handleIllegalPrimaryKeyException(IllegalPrimaryKeyException exception, Locale locale) {
return handleApplicationException(exception, locale);
}
@ExceptionHandler(Throwable.class)
@ResponseStatus(value = HttpStatus.INTERNAL_SERVER_ERROR)
@ResponseBody
public ModelAndView handleThrowable(Throwable exception, Locale locale) {
LOG.error(exception.getMessage(), exception);
return new ErrorView(createErrorBean(ErrorBean.UNEXPECTED_ERROR_KEY, locale, exception)).asView();
}
}